﻿
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Asistente.Model.Representation;

namespace Asistente.Model.COSMIC
{
    public class CosmicConverter
    {
        private static CosmicConverter instance = new CosmicConverter();
        public static CosmicConverter Instance
        {
            get
            {
                if (instance == null)
                    instance = new CosmicConverter();
                return instance;
            }
        }

        /// <summary>
        /// This method converts the application model in a Cosmic measurement model
        /// </summary>
        /// <param name="app"> The application to convert</param>
        /// <returns>The converted Cosmic Model </returns>
        public CosmicModel Convert(Application app)
        {
            CosmicModel model = new CosmicModel(app);
            foreach (UseCase uc in app.UseCases)
            {
                FunctionalProcess fp = new FunctionalProcess("FP - " + uc.Name);
                model.addFunctionalProcess(fp);
                fp = ConvertUseCase(uc, fp);
            }
            return model;
        }

        #region ConvertUseCase
        /// <summary>
        /// This method convert one use case in a Cosmic Functional Process
        /// </summary>
        /// <param name="useCase">The use Case to convert</param>
        /// <param name="fp"> The resulting functional process</param>
        /// <returns></returns>
        private FunctionalProcess ConvertUseCase(UseCase useCase, FunctionalProcess fp)
        {
            if (useCase.OverwrittenMeasuresCosmic)
            {
                fp = ConvertOverwriteUseCase(useCase, fp);
            }
            else
            {
                if (useCase.DisplayMessage)
                {
                    fp.AddExitMovement(new InterestObject("Messages " + useCase.Name));
                }
                if (useCase.Auditable)
                {
                    fp.AddExitMovement(new InterestObject("Is Auditable " + useCase.Name));
                    fp.AddEntryMovement(new InterestObject("Is Auditable " + useCase.Name));
                    fp.AddReadMovement(new InterestObject("Is Auditable " + useCase.Name));
                    fp.AddWriteMovement(new InterestObject("Is Auditable " + useCase.Name));
                }

                foreach (Screen screen in useCase.Screens)
                {
                    /* foreach (Asistente.Model.Representation.Action action in screen.Actions)
                     {
                         La navegacion entre interfaz no se cuenta como movimiento de datos.
                         Consultar Cosmic Measurement manual v3.0.1
                         fp.AddExitMovement(new InterestObject("Triggers " + action.Name));
                     }*/
                    foreach (MasterDetail md in screen.MasterDetails)
                    {
                        fp = ConvertMasterDetail(fp, screen, useCase, md);
                    }

                    Type type;
                    foreach (Panel panel in screen.Panels)
                    {
                        type = panel.GetType();
                        if (typeof(EditablePanel).IsAssignableFrom(type))
                        {
                            fp = ConvertEditablePanel(fp, useCase, screen, (EditablePanel)panel);
                        }
                        else
                        {
                            if (typeof(ReadOnlyPanel).IsAssignableFrom(type))
                            {
                                fp = ConvertReadOnlyPanel(fp, screen, useCase, (ReadOnlyPanel)panel);
                            }
                        }
                    }
                }
            }

            return fp;
        }
        #endregion

        #region ConvertEditablePanel
        /// <summary>
        /// This method adds the corresponding movements to an editable panel into a functional process
        /// </summary>
        /// <param name="fp">The functional process to add the movements</param>
        /// <param name="useCase">The owner Use case  of the panel to convert</param>
        /// <param name="screen">The screen which contains the panel to convert</param>
        /// <param name="panel">The editable panel to convert</param>
        /// <returns>The resulting Functional Process</returns>
        private FunctionalProcess ConvertEditablePanel(FunctionalProcess fp, UseCase useCase, Screen screen, EditablePanel panel)
        {
            //si es un editable panel puede ser filter grid o form
            //si es un filter no produce escrituras 
            if (panel.GetType() == typeof(Filter))
            {
                if (panel.Entity != null)
                {
                    fp.AddReadMovement(new InterestObject(panel.Entity.Name));
                }
                //luego de leer se muestra en la interfaz --> X 
                if (HaveRelatedPanels(panel, useCase))
                {
                    fp.AddExitMovement(new InterestObject(panel.Name));
                }
            }
            else
            {
                if (panel.Entity != null)
                {
                    //si el panel no es de tipo Filter entonces necesariamente hace una escritura --> W
                    //Si es un caso de uso Login o Search o list el panel no hace ninguna escritura en las entidades vinculadas al mismo
                    if ((useCase.Pattern != UseCasePattern.Login) && (useCase.Pattern != UseCasePattern.Search) && (useCase.Pattern != UseCasePattern.List) && (useCase.Pattern != UseCasePattern.View))
                    {
                        fp.AddWriteMovement(new InterestObject(panel.Entity.Name));
                    }
                    //antes de escribir una entidad se verifica que la instancia a borrar exista--> R
                    fp.AddReadMovement(new InterestObject(panel.Entity.Name));
                    //
                }

                //si es un caso de uso New  o Phisical delete o login no se deben escribir las entidades relacionadas al panel
                if ((useCase.Pattern != UseCasePattern.LogicDelete))
                {
                    fp = ConvertEditablePanelUpDelCascadeEntities(fp, useCase, panel);
                }
            }

            fp = ConvertPanelsControls(fp, screen, useCase, panel);
            return fp;
        }
        /// <summary>
        /// this method adds the corresponding movements related with the panel updateDeleteCascade entities 
        /// </summary>
        /// <param name="fp">The functional process to add the movements</param>
        /// <param name="useCase">The owner Use  case  of the panel to convert</param>
        /// <param name="panel">The editable panel to convert</param>
        /// <returns>The resulting Functional Process</returns>
        private FunctionalProcess ConvertEditablePanelUpDelCascadeEntities(FunctionalProcess fp, UseCase useCase, EditablePanel panel)
        {
            foreach (Entity entity in panel.UpdatesDeletesCascade)
            {

                //dependiendo del patron del caso de uso es posible o no realizar escrituras en entidades asociadas
                if ((useCase.Pattern != UseCasePattern.Login) && (useCase.Pattern != UseCasePattern.Search) &&
                    (useCase.Pattern != UseCasePattern.List) && (useCase.Pattern != UseCasePattern.New) &&
                    (useCase.Pattern != UseCasePattern.LogicDelete) && (useCase.Pattern != UseCasePattern.View))
                {
                    fp.AddWriteMovement(new InterestObject(entity.Name));
                }

                //para escribir una entidad referenciada antes tambien hace una lectura para verificar que existan
                fp.AddReadMovement(new InterestObject(entity.Name));
                //
            }
            return fp;
        }


        /// <summary>
        /// This method adds the correponding movements of the panels controls into a functional process
        /// </summary>
        /// <param name="fp">The functional process to add the movements</param>
        /// <param name="useCase">The  owner Use case of the panel to convert</param>
        /// <param name="panel">The editable panel to convert</param>
        /// <returns>The resulting Functional Process</returns>
        public FunctionalProcess ConvertPanelsControls(FunctionalProcess fp, Screen screen, UseCase useCase, Panel panel)
        {
            foreach (Control ctrl in panel.Controls)
            {
                if (ctrl.Quantity > 0)
                {
                    switch (ctrl.ControlPattern)
                    {
                        case ControlPattern.Autocomplete:
                            //agrega una lectura por cada entidad vinculada 
                            //agrega una salida al panel 
                            //agrega una entrada cuando se selecciona una opcion
                            if (ctrl.DataGroup != null)//si se definio un DataGroup entonces hay una lectura de cada entidad del dataGroup y una salida para el control
                            {
                                Query query = ctrl.DataGroup as Query;
                                foreach (Entity entity in query.UsedEntities)
                                {
                                    fp.AddReadMovement(new InterestObject(/*panel.Name + "-" + ctrl.Name + "-" */entity.Name));
                                }
                                fp.AddExitMovement(new InterestObject(panel.Name + "-" + ctrl.Name));
                            }
                            fp.AddEntryMovement(new InterestObject(panel.Name));
                            break;

                        case ControlPattern.CalculatedField:
                            //agrega una lectura por cada entidad vinculada 
                            //agrega una salida al panel
                            if (ctrl.DataGroup != null)//si se definio un DataGroup entonces hay una lectura de cada entidad del dataGroup y una salida para el control
                            {
                                Query query = ctrl.DataGroup as Query;
                                foreach (Entity entity in query.UsedEntities)
                                {
                                    fp.AddReadMovement(new InterestObject(/*panel.Name + "-" + ctrl.Name + "-" */entity.Name));//si la entidad ya fue leida no se debe agregar
                                }
                                fp.AddExitMovement(new InterestObject(panel.Name + "-" + ctrl.Name));
                            }
                            break;

                        case ControlPattern.DropDownChooser:
                            //agrega una lectura por cada entidad vinculada
                            //agrega una salida cuando se despliega
                            //agrega una Entrada cuando se selecciona una opcion

                            if (ctrl.DataGroup != null)//si se definio un DataGroup entonces hay una lectura de cada entidad del dataGroup y una salida para el control
                            {
                                Query query = ctrl.DataGroup as Query;
                                foreach (Entity entity in query.UsedEntities)
                                {
                                    fp.AddReadMovement(new InterestObject(/*panel.Name + "-" + ctrl.Name + "-" + */entity.Name));
                                }
                                fp.AddExitMovement(new InterestObject(panel.Name + "-" + ctrl.Name));
                            }
                            fp.AddEntryMovement(new InterestObject(panel.Name));

                            break;

                        case ControlPattern.DropDownList:
                            //agrega una salida cuando se despliega 
                            fp.AddExitMovement(new InterestObject(panel.Name + "-" + ctrl.Name));
                            //agrega una Entrada cuando se selecciona una opcion
                            fp.AddEntryMovement(new InterestObject(panel.Name));
                            break;


                        case ControlPattern.PatternEdit:
                            fp.AddEntryMovement(new InterestObject(panel.Name));
                            break;

                        case ControlPattern.PlainTextInput:
                            fp.AddEntryMovement(new InterestObject(panel.Name));
                            break;

                        case ControlPattern.PlainTextOutput:
                            //Agrega una salida al panel
                            fp.AddExitMovement(new InterestObject(panel.Name));
                            break;

                        case ControlPattern.RadioButton:
                            fp.AddEntryMovement(new InterestObject(panel.Name));
                            break;

                        case ControlPattern.SlideControl:
                            fp.AddEntryMovement(new InterestObject(panel.Name));
                            break;

                        case ControlPattern.SmallSetMultipleSelector:
                            fp.AddEntryMovement(new InterestObject(panel.Name));
                            break;

                        case ControlPattern.SmallSetSingleSelector:
                            fp.AddEntryMovement(new InterestObject(panel.Name));
                            break;

                        case ControlPattern.CheckBox:
                            fp.AddEntryMovement(new InterestObject(panel.Name));
                            break;

                        case ControlPattern.DatePicker:
                            fp.AddEntryMovement(new InterestObject(panel.Name));
                            break;

                        case ControlPattern.DateRangeInput:
                            fp.AddEntryMovement(new InterestObject(panel.Name));
                            break;

                        case ControlPattern.StructuredEdit:
                            fp.AddEntryMovement(new InterestObject(panel.Name));
                            break;
                    }
                }
            }
            return fp;
        }

        #endregion

        #region convertReadOnlyPanel
        /// <summary>
        /// This method adds the correponding movements of a ReadOnly panel into a functional process
        /// </summary>
        /// <param name="fp">The functional process to add the movements</param>
        /// <param name="panel">The editable panel to convert</param>
        /// <returns>The resulting Functional Process</returns>
        private FunctionalProcess ConvertReadOnlyPanel(FunctionalProcess fp, Screen screen, UseCase useCase, ReadOnlyPanel panel)
        {
            //En este caso no se tienen en cuentra triggers para ejecutar los casos de uso ya que
            //son activados desde otros casos de uso mediante acciones

            //El panel tiene una unica salida
            fp.AddExitMovement(new InterestObject(panel.Name));

            //El dataGroup define las entidades que deben ser leidas
            if (typeof(Entity).IsAssignableFrom(panel.DataGroup.GetType()))
            {
                Entity entity = panel.DataGroup as Entity;
                fp.AddReadMovement(new InterestObject(entity.Name));
            }
            else
            {
                Query query = (Query)panel.DataGroup;
                if (query != null)
                {
                    foreach (Entity entity in query.UsedEntities)
                    {
                        fp.AddReadMovement(new InterestObject(entity.Name));
                    }
                }
            }
            fp = ConvertPanelsControls(fp, screen, useCase, panel);
            return fp;
        }
        #endregion

        #region ConvertMasterDetail
        /// <summary>
        /// This method adds the correponding movements of a MasterDetail into a functional process
        /// </summary>
        /// <param name="fp">The functional process to add the movements</param>
        /// <param name="useCase">The owner Use case  of the masterDetail to convert</param>
        /// <param name="md">The MasterDetail to convert</param>
        /// <returns>The resulting Functional Process</returns>
        public FunctionalProcess ConvertMasterDetail(FunctionalProcess fp, Screen screen, UseCase useCase, MasterDetail md)
        {
            /*  
             * Si el master es de tipo filter       entonces suma  E, R, X     y por cada detail R, W, y X
             * Si el master es de tipo Grid o Form  entonces suma  E, R, W  y por cada detail R, W, y X
            */
            EditablePanel master = md.Master;
            if (master != null)
            {
                if (master.GetType() == typeof(Filter))
                {
                    if (master.Entity != null)
                    {
                        fp.AddReadMovement(new InterestObject(master.Entity.Name));
                    }
                    //luego de leer se muestra en la interfaz --> X 

                    fp.AddExitMovement(new InterestObject(master.Name));
                    fp = ConvertPanelsControls(fp, screen, useCase, master);
                    fp = ConvertDetailPanels(fp, screen, useCase, md);
                }
                else//si master es de tipo Form o Grid
                {

                    if (master.Entity != null)
                    {

                        //si el panel no es de tipo Filter entonces necesariamente hace una escritura --> W
                        //Si es un caso de uso Login o Search o list o view el panel no hace ninguna escritura en la entidades principal vinculada al mismo
                        if ((useCase.Pattern != UseCasePattern.Login) || (useCase.Pattern != UseCasePattern.Search) ||
                            (useCase.Pattern != UseCasePattern.View) || (useCase.Pattern != UseCasePattern.List))
                        {
                            fp.AddWriteMovement(new InterestObject(master.Entity.Name));
                        }
                        //antes de escribir una entidad se verifica que la instancia a borrar exista --> R
                        fp.AddReadMovement(new InterestObject(master.Entity.Name));
                        //
                    }
                    fp = ConvertEditablePanelUpDelCascadeEntities(fp, useCase, master); //las entidades relacionadas pueden escribir dependiendo patron de casos de uso
                    fp = ConvertPanelsControls(fp, screen, useCase, master);
                }
            }
            fp = ConvertDetailPanels(fp, screen, useCase, md);

            return fp;
        }

        /// <summary>
        /// This method adds the correponding movements of the Details panels of a MasterDetail into a functional process
        /// </summary>
        /// <param name="fp">The functional process to add the movements</param>
        /// <param name="useCase">The owner Use case  of the masterDetail to convert</param>
        /// <param name="md">The editable useCase to convert</param>
        /// <returns>The resulting Functional Process</returns>
        private FunctionalProcess ConvertDetailPanels(FunctionalProcess fp, Screen screen, UseCase useCase, MasterDetail md)
        {
            foreach (EditablePanel detail in md.Details)
            {
                if (detail.GetType() == typeof(Filter))
                {
                    if (detail.Entity != null)
                    {
                        fp.AddReadMovement(new InterestObject(detail.Entity.Name));
                    }
                    //luego de leer se muestra en la interfaz --> X
                    if (HaveRelatedExitControls(detail, md))
                    {
                        fp.AddExitMovement(new InterestObject(detail.Name));
                    }
                }
                else
                {
                    if (detail.Entity != null)
                    {
                        //si el panel no es de tipo Filter entonces puede hacer una escritura  --> W
                        //Si es un caso de uso Login o Search o list el panel no hace ninguna escritura en las entidades vinculadas al mismo
                        if ((useCase.Pattern != UseCasePattern.Login) && (useCase.Pattern != UseCasePattern.Search) &&
                            (useCase.Pattern != UseCasePattern.List) && (useCase.Pattern != UseCasePattern.View))
                        {
                            fp.AddWriteMovement(new InterestObject(detail.Entity.Name));
                        }
                        // se verifica que la instancia a actualizar exista --> R
                        fp.AddReadMovement(new InterestObject(detail.Entity.Name));
                        //
                    }

                    //si es un caso de uso New  o Phisical delete o login no se deben escribir las entidades relacionadas al panel
                    //se controlan dos veces para que sea mas claro el codigo
                    if ((useCase.Pattern != UseCasePattern.New) && (useCase.Pattern != UseCasePattern.LogicDelete) &&
                        (useCase.Pattern != UseCasePattern.Login) && (useCase.Pattern != UseCasePattern.Login))
                    {
                        fp = ConvertEditablePanelUpDelCascadeEntities(fp, useCase, detail);
                    }

                }

                fp = ConvertPanelsControls(fp, screen, useCase, detail);
            }
            return fp;
        }
        #endregion

        #region ConvertOverwriteUseCase
        public FunctionalProcess ConvertOverwriteUseCase(UseCase useCase, FunctionalProcess fp)
        {
            for (Int32 i = 1; i <= useCase.Entries; i++)
            {
                fp.AddEntryMovement(new InterestObject("overwrited Entry" + i));
            }
            for (Int32 i = 1; i <= useCase.Exits; i++)
            {
                fp.AddExitMovement(new InterestObject("overwrited Exit" + i));
            }
            for (Int32 i = 1; i <= useCase.Reads; i++)
            {
                fp.AddReadMovement(new InterestObject("overwrited Read" + i));
            }
            for (Int32 i = 1; i <= useCase.Writes; i++)
            {
                fp.AddWriteMovement(new InterestObject("overwrited Write" + i));
            }

            return fp;
        }


        #endregion


        #region utils

        /// <summary>
        /// Returns true if the screen contains a related panel containing an exit control 
        /// two panels are related if they have a common related entity
        /// </summary>
        /// <param name="panel"></param>
        /// <param name="screen"></param>
        /// <returns></returns>
        public Boolean HaveRelatedPanels(EditablePanel panel, UseCase useCase)
        {   //si el panel no tiene una entidad asociada entonces filtra sobre resultados ya obtenidos
            foreach (Screen screen in useCase.Screens)
            {
                if (panel.Entity != null)
                {
                    foreach (Panel screenPanel in screen.Panels)
                    {
                        //foreach (Control control in screenPanel.Controls)

                        if (screenPanel != panel)
                        {
                            if (typeof(EditablePanel).IsAssignableFrom(screenPanel.GetType()))
                            {
                                EditablePanel editablePanel = screenPanel as EditablePanel;

                                if (editablePanel.Entity != null)
                                {

                                    if ((editablePanel.UpdatesDeletesCascade.Contains<Entity>(panel.Entity)) || (editablePanel.Entity.Name.Equals(panel.Entity.Name)))
                                    {
                                        if (!haveOutputControls(editablePanel))
                                        {
                                            return true;
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
            return false;
        }
        /// <summary>
        /// Returns true if the masterDetail contains a related panel containing an exit control 
        /// two panels are related if they have a common related entity
        /// </summary>
        public Boolean HaveRelatedExitControls(EditablePanel panel, MasterDetail md)
        {
            foreach (Panel screenPanel in md.Details)
            {
                //foreach (Control control in screenPanel.Controls)
                if (screenPanel != panel)
                {
                    //if (control.ControlPattern == ControlPattern.PlainTextOutput)
                    // {
                    if (typeof(EditablePanel).IsAssignableFrom(screenPanel.GetType()))
                    {
                        EditablePanel editablePanel = screenPanel as EditablePanel;
                        if ((editablePanel.UpdatesDeletesCascade.Contains<Entity>(panel.Entity)) || (editablePanel.Entity.Name.Equals(panel.Entity.Name)))
                        {
                            return true;
                        }
                    }
                    // }
                }
            }
            return false;
        }

        /// <summary>
        /// This method checks one panel have plainTextOutpu controls
        /// </summary>
        /// <param name="panel">The panel to check</param>
        /// <returns>True if the panel contains plainTextOuptus</returns>
        public Boolean haveOutputControls(EditablePanel panel)
        {

            foreach (Control ctrl in panel.Controls)
            {
                if (ctrl.ControlPattern == ControlPattern.PlainTextOutput)
                {
                    return true;
                }
            }
            return false;
        }
        #endregion
    }
}
